﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Infraestruturex.BaseLibrary;
using Compilex.Syntatic.Descriptors;

namespace Compilex.Syntatic.Declarations
{
    public class ArithmeticExpression
    {

        public SyntaticContext Context { get; set; }
        public SymbolType Type { get; set; }
        private SimpleStack<ArithmeticOperand> OperandStack { get; set; }
        private SimpleStack<string> OperatorStack { get; set; }
        private string debugString = "";


        public ArithmeticExpression(SyntaticContext context)
        {
            this.Context = context;
            this.OperandStack = new SimpleStack<ArithmeticOperand>();
            this.OperatorStack = new SimpleStack<string>();
        }

        public void AddSimpleIdOperand()
        {
            string id = Context.CurrentToken;
            debugString += id;
            Symbol sym = SymbolTableHelper.GetVariableDeclaredAnywhere(id, Context);
            ArithmeticOperand op = new ArithmeticOperand() { IsSymbol = true, Symbol = sym };
            OperandStack.Push(op);
            UpdateExpressionType(sym);
        }

        private void UpdateExpressionType(Symbol sym)
        {
            if (this.Type == null)
            {
                this.Type = new SymbolType(((VariableDescriptor)sym.Descriptor).Type);
            }
            else
            {
                // necessariamente type é intex
                if (this.Type.TypeValue == TypeValue.intex)
                {
                    if (((VariableDescriptor)sym.Descriptor).Type.TypeValue != TypeValue.intex)
                    {
                        throw new SyntaticException("Tipos incompatíveis: '" + sym.Identifier + "' deve ser do tipo 'intex'.");
                    }
                }
                else
                {
                    throw new InternalSyntaticException("Erro de invariante: o tipo de uma expressao com dois operandos nao e' intex.");
                }
            }
        }

        public void AddArrayElementOperand()
        {
            // este método é chamado quando o offset é lido
            string strOffset = Context.CurrentToken;
            Context.SyntaticStack.Pop(); // [
            string arrayId = Context.SyntaticStack.Pop();
            Symbol array = SymbolTableHelper.GetArrayDeclaredAnywhere(arrayId, Context);
            VariableDescriptor descriptor = (VariableDescriptor)array.Descriptor;
            int offset = Int32.Parse(strOffset);
            if (offset < descriptor.Size)
            {
                ArithmeticOperand op = new ArithmeticOperand() { IsArrayElement = true, Array = array, ArrayOffset = offset };
                OperandStack.Push(op);
                UpdateExpressionType(array);
            }
            else
                throw new SyntaticException("O vetor " + array.Identifier + " tem somente " + descriptor.Size + " elementos.");
        }

        public void AddConstantOperand()
        {
            string strConstant = Context.CurrentToken;
            debugString += strConstant;
            int constant = int.Parse(strConstant);
            AddConstantOperand(constant);
        }

        private void AddConstantOperand(int constant)
        {
            ArithmeticOperand op = new ArithmeticOperand() { IsConstant = true, Value = constant };
            OperandStack.Push(op);

            if (this.Type == null)
            {
                this.Type = new SymbolType(TypeValue.intex);
            }
        }

        public void AddOperator()
        {
            if (OperandStack.Count == 0)
                AddConstantOperand(0);

            if (this.Type.TypeValue != TypeValue.intex)
            {
                throw new SyntaticException("Um operador aritimético não pode ser aplicado a uma expressão do tipo " + this.Type);
            }
            string _operator = Context.CurrentToken;
            debugString += _operator;
            switch (_operator)
            {
                case "+":
                case "-":
                    PlusMinusOperator(_operator);
                    break;
                case "*":
                case "/":
                    TimesDivisionOperator(_operator);
                    break;
                default:
                    throw new InternalSyntaticException("Operador '" + _operator + " não reconhecido");
            }
            this.Type.TypeValue = TypeValue.intex;
        }

        public void ExpressionEnded()
        {
            if (OperatorStack.Count > 0)
            {
                GenerateCode();
                ExpressionEnded();
            }
            if(OperandStack.Count > 1) throw new InternalSyntaticException("Erro de invariante: pilha de operando contém " + OperandStack.Count + " elementos no fim da expressão");
            if (OperandStack.Count > 0 && !OperandStack.Peek().IsTemporary)
            {
                Context.CommandBlockStack.Current.CodeGenerator.AddArithmeticOperation(OperandStack.Pop());
                OperandStack.Push(new ArithmeticOperand()
                {
                    IsTemporary = true,
                    TempLabel = Context.CommandBlockStack.Current.CodeGenerator.GetLatestTempLabel()
                });
            }
        }

        public void AddLeftParenthesis()
        {
            OperatorStack.Push("(");
        }

        public void BExpressionEnded()
        {
            if (this.Type == null)
                this.Type = new SymbolType(Context.ExpressionStack.JustPopped.Type);
            else
            {
                if (this.Type != Context.ExpressionStack.JustPopped.Type)
                {
                    throw new SyntaticException("Não se pode combinar operandos dos tipos '" +
                      this.Type + "' e '" + Context.ExpressionStack.JustPopped.Type + "'");
                }
            }
            this.OperandStack.Push(
                new ArithmeticOperand()
                {
                    IsTemporary = true,
                    TempLabel = Context.CommandBlockStack.Current.CodeGenerator.GetLatestTempLabel()
                });
        }

        public void StartFunctionCall()
        {
            this.Context.FunctionCallStack.StartFunctionCall();
            FunctionDescriptor fDescriptor = this.Context.FunctionCallStack.Current.FunctionDescriptor;

            if(fDescriptor.IsVoid)
                throw new SyntaticException("Função "+Context.FunctionCallStack.Current.FunctionName+ " void não pode ser chamada em uma expressão.");

            if (this.Type == null)
                this.Type = new SymbolType(fDescriptor.ReturnType);
            else
            {
                if (this.Type != fDescriptor.ReturnType)
                {
                    throw new SyntaticException("Não se pode combinar operandos dos tipos '" +
                      this.Type + "' e '" + fDescriptor.ReturnType + "'");
                }
            }

            debugString += "funcex";

        }

        public void EndFunctionCall()
        {
            this.Context.FunctionCallStack.EndFunctionCall();

            OperandStack.Push(new ArithmeticOperand()
            {
                IsTemporary = true,
                TempLabel = Context.CommandBlockStack.Current.CodeGenerator.GetLatestTempLabel()
            });

        }

        private void PlusMinusOperator(string _operator)
        {
            if (OperatorStack.Count > 0)
            {
                string _stackOperator = OperatorStack.Peek();
                if (_stackOperator == "+" || _stackOperator == "-"
                    || _stackOperator == "*" || _stackOperator == "/")
                {
                    GenerateCode();
                    PlusMinusOperator(_operator);
                    return;
                }
            }
            OperatorStack.Push(_operator);
        }

        private void GenerateCode()
        {
            ArithmeticOperand second = OperandStack.Pop();
            ArithmeticOperand first = OperandStack.Pop();
            string expressionOperator = OperatorStack.Pop();
            string tempVarName = Context.CommandBlockStack.Current.CodeGenerator.AddArithmeticOperation(
                first, second, expressionOperator);
            OperandStack.Push(new ArithmeticOperand()
            {
                IsTemporary = true,
                TempLabel = tempVarName
            });
        }

        private void TimesDivisionOperator(string _operator)
        {
            if (OperatorStack.Count > 0)
            {
                string _stackOperator = OperatorStack.Peek();
                if (_stackOperator == "*" || _stackOperator == "/")
                {
                    GenerateCode();
                    TimesDivisionOperator(_operator);
                    return;
                }
            }
            OperatorStack.Push(_operator);
        }

        public override string ToString()
        {
            return debugString;
        }
    }
}
